Skip to Content

12장 함수

12-1 함수란?

함수는 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다. 또한 함수는 입력을 받아서 출력을 내보내며, 함수에 사용되는 세부적인 구성요소는 아래와 같다.

image.png

  • 매개변수 : 함수 내부로 입력을 전달받는 변수
  • 인수 : 입력
  • 반환값 : 출력

이때, 함수는 값이며, 여러 개 존재할 수 있으므로 함수를 구별하기 위해 이름을 사용할 수 있다.

함수 f(x,y) = x+y 를 자바스크립트의 함수로 표현하면 아래와 같다.

//함수 정의 function add(x, y) { return x + y; } // 함수 호출 add(2, 5); // 7

함수는 함수 정의를 통해 생성한다. 하지만 함수 정의만으로 함수가 실행되는 것은 아니다. 함수를 실행하기 위해 함수 호출을 해야하며, 이는 인수를 매개변수를 통해 함수에 전달하여 명시적으로 실행을 지시해야한다.

12-2 함수를 사용하는 이유

함수의 장점

  • 필요할 때마다 한번 정의해 둔 것을 호출만 하면서 재사용할 수 있다.
  • 유지보수의 편의성 증진 및 코드의 신뢰성이 향상된다.
  • 반복되는 코드를 줄이므로 코드의 가독성 향상 효과가 있다.
var x = 0; var y = 0; var result = 0; x = 1; y = 2; result = x + y; // 3 x = 3; y = 4; result = x + y; // 7 x = 5; y = 6; result = x + y; // 11

위 코드는 함수를 사용하지 않은 탓에 같은 동작의 코드를 일일히 3번씩이나 작성하였다.

function add(x, y) { return x + y; } var result = 0; result = add(1, 2); // 3 result = add(3, 4); // 7 result = add(5, 6); // 11

위 코드는 덧셈 및 덧셈 결과를 반환하는 함수를 작성하여 result 변수에 담는코드 한 줄씩만 반복되고있다. 즉, 함수를 사용하면 가독성 향상 및 동일 로직을 재사용하기 좋다.

“코드는 동작하는 것만이 존재 목적이 아니다. 코드는 개발자를 위한 문서이며, 남이 이해할 수 있는 코드, 즉 가독성이 좋은코드가 좋은 코드다.” - deepdive

12-3 함수 리터럴

자바스크립트의 함수는 객체 타입의 “값”이다. 따라서 숫자 값을 리터럴로 생성하고, 객체를 객체 리터럴로 생성하듯이 함수도 함수 리터럴로 생성할 수 있다. 이때 함수 리터럴도 평가되어 값을 생성하며, 이 값은 객체이다. 즉, 함수는 객체이다.(js 만의 특징)

(*참고로 리터럴은 값을 생성하기 위한 표기법을 뜻한다.)

함수 리터럴 구성

  • function 키워드
  • 함수 이름 :
    • 함수 이름은 식별자이므로 네이밍 규칙을 준수해야 한다.
    • 함수 이름은 함수 body 내에서만 참조할 수 있는 식별자이다.
    • 함수 이름은 생략할 수있고, 이름이 없는 함수는 익명함수가 된다.
  • 매개변수 목록
    • 0개 이상의 매개변수를 소괄호가 감싸고 쉼표로 구분
    • 각 매개변수에 함수를 호출할 때 지정한 인수가 순서대로 할당된다. 즉, 순서가 의미가 있다.
    • 매개변수는 함수 body 내에서 변수와 동일하게 취급된다. 따라서 매개변수도 네이밍 규칙을 준수해야 한다.
  • 함수 body
    • 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행단위로 정의한 코드 블럭.
    • 함수 몸체는 함수 호출에 의해 실행된다.
// 변수에 함수 리터럴을 할당 var f = function add(x, y) { return x + y; };

위 예제는 함수 리터럴을 변수에 할당하고 있다.

함수(객체) vs 일반 객체 차이점 비교

-> 일반객체는 호출할 수 없지만 함수는 호출할 수 있다.

-> 함수 객체만의 고유한 프로퍼티를 갖는다.

12-4 함수 정의

함수 정의 : 함수 호출하기 이전, 매개변수, 실행문, 반환값을 지정하는 것.

정의된 함수는 js 엔진에 의해 평가되어 함수 객체가 된다.

함수 정의하는 방법

  1. 함수 선언문
// 함수 선언문 function add(x, y) { return x + y; } //함수 호출 console.log(add(2,5)) // 7 // 함수 선언문에서의 함수 이름 생략 function (x, y) { return x + y; } // SyntaxError : Function statements require a function name
  • 함수 선언문은 함수 리터럴과 형태가 동일하다. 단, 함수 리터럴은 함수 이름을 생략할 수 있으나, 함수 선언문은 함수 이름을 생략할 수 없다.

  • 함수 선언문은 “표현식이 아닌 문”이다. 만약 함수 선언문이 “표현식인 문”이라면, 완료 값 undefined 대신 표현식이 평가되어 생성된 함수가 출력되어야 한다. image.png 즉, 표현식이 아닌 문 이므로 변수에 할당할 수 없다. 하지만, 위 예제를 보면 함수 선언문이 변수에 할당 되는 것 처럼 보인다.

    -> 왜그럴까? JS 엔진이 코드의 문맥에 따라 동일한 함수 리터럴을 함수 선언문으로 해석하는 경우와 함수 리터럴 표현식으로 해석하는 경우가 있기때문이다! "" 는 블록문일 수도 있고, 객체 리터럴 일 수도있다. 즉, 중의적 표현이다. 그렇다면 JS엔진은 이를 어떻게 처리할까? -> 문맥에 따라 해석이 달라진다.

    마찬가지로, 기명함수 리터럴도 중의적인 코드이다. 따라서 코드 문맥에 따라 해석이 달라질 수 있는데, 함수 리터럴을 변수에 할당하거나 피연산자로 사용하면 함수 리터럴 표현식으로 해석한다.

  • 함수는 함수 이름으로 호출하는 것이 아닌, 함수 객체를 가르키는 식별자로 호출한다. image.png js 엔진이 암묵적으로 생성한 식별자 add가 함수를 호출한 것이다!

  1. 함수 표현식
var add = function (x, y) { return x + y; }; console.log(add(2, 5)); // 7

자바스크립트에서 함수는 일급 객체이다.

  • 함수가 값처럼 변수에 할당될 수 있다.
  • 함수가 프로퍼티 값이 될 수 있다.
  • 함수가 배열의 요소가 될 수 있다.

즉, 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있으며, 이를 함수 표현식 이라고 한다.

  1. Fuction 생성자 함수
var add = new Function('x', 'y', 'return x+y'); console.log(add(2, 5)); // 7

자바스크립트가 기본 제공하는 빌트인 함수 Function 생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달하고, new 연산자와 함께 호출하면 된다.(new 연산자 없이 호출해도 결과는 동일하다.)

  1. 화살표 함수(ES6)
var add = (x, y) => x + y; console.log(add(2, 5)); // 7

26.3 절 “화살표 함수”에서 더 알아볼 예정이나, 형태는 봐두도록 하자!!

함수 생성 시점과 호이스팅

// 함수 참조 console.dir(add); // f add(x, y) console.dir(sub); // undefined // 함수 호출 console.log(add(2, 5)); // 7 console.log(sub(2, 5)); // TypeError: sub is not a function // 함수 선언문 function add(x, y) { return x + y; } // 함수 표현식 var sub = function (x, y) { return x - y; };
  • 함수 선언문으로 정의된 함수 : 호이스팅 가능
  • 함수 표현식으로 정의된 함수 : 호이스팅 불가

왜? -> 각각의 방식은 함수의 생성 시점이 다르기 때문이다.

함수 선언문 : 모든 선언문처럼 런타임 이전에 js 엔진에 의해 먼저 실행된다. 따라서, 런타임 이전에 함수 객체가 먼저 생성되고, js 엔진은 함수 이름과 동일한 이름의 식별자를 생성하고 생성된 함수 객체를 할당한다.(함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.) -> 함수 선언문이 코드의 맨 위로 끌어 올려진 것처럼 동작하는 함수 호이스팅 발생

함수 표현식 : 변수에 할당되는 값이 함수 리터럴인 문이다. 따라서 변수 호이스팅이 발생한다. 변수 선언은 런타임 이전에 실행되며 undefined 로 초기화 된다. 따라서 함수표현식또한 함수표현식이 작성된 부분 이전에 함수를 참조하면 undefined로 평가된다. 이는 타입 에러를 발생시킨다.

예제 : 다음 실행 결과를 예상해봅시다.

// 아래 코드의 실행 가능 여부 및 이유를 이야기해보자. // 1. console.log(hello()); function hello() { return "Hi!"; } =========================================== // 2. console.log(sayHi()); var sayHi = function() { return "Hello!"; }; ========================================= // 3. add(); // ? sub(); // ? function add() { console.log("A"); } var sub = function() { console.log("B"); };

해답

  1. 실행됩니다! 함수 선언문(function hello() )은 호이스팅 시점에 전체 함수 정의가 끌어올려지기 때문

  2. 실행되지 않습니다! var 변수 선언은 호이스팅되지만, 함수 표현식의 할당은 호이스팅되지 않습니다. 즉, sayHi 는 선언 시점에는 undefined 상태이므로 undefined()->TypeError: sayHi is not a function 발생

  3. add() → “A” 출력됨 (함수 선언문은 호이스팅되어 정의가 미리 메모리에 올라감)
    sub() → TypeError: sub is not a function (변수 sub은 선언만 호이스팅되어 undefined 상태)

12-5 함수 호출

함수가 호출되면 현재 실행 흐름을 중단하고, 호출된 함수로 실행 흐름을 옮긴다. 매개변수에 인수가 순서대로 할당되고, 함수 몸체의 문들이 실행된다.

image.png

매개변수는 함수 몸체 내부에서 변수와 동일하게 취급된다. 이는 일반변수와 마찬가지로 undefined로 초기화 된 후 인수가 순서대로 할당된다.

function add(x, y) { console.log(x, y); // 2 5 return x + y; } add(2, 5); // add 함수의 매개변수 x, y는 함수 몸체 내부에서만 참조 가능 console.log(x, y); // ReferenceError: x is not defined
  • 매개 변수는 함수 몸체 외부에서 참조할 수 없다.
function add(x, y) { return x + y; } console.log(add(2)); // NaN
  • 함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지않는다.
    • 개수가 다르더라도 에러가 발생하지 않는다.
  • 위의 경우 x는 2 할당, y는 undefined가 되고, 이는 2 + undefined = NaN 을 반환한다.
function add(x, y) { return x + y; } console.log(add(2, 5, 10)); // 7
  • 이번에는 인수가 매개변수 개수보다 많은 경우이다.
  • 초과된 인수는 그냥 버려지는 것은 아니고, 암묵적으로 arguments 객체의 프로터피에 보관된다.
  • 계산 결과는 필요한 인수를 “순서대로” 할당하여 2 + 5 = 7 결과가 도출된 것이다.
function add(x, y) { console.log(arguments); // Arguments(3) [2,5,10, callee: f. Symbol(Symbol.iterator): f] return x + y; } add(2, 5, 10);
  • arguments 프로퍼티는 18장에서 더 자세히 다룰 예정이니 간단히 확인만 해두자!
function add(x, y) { return x + y; } console.log(add(2, 3)); // 5 console.log(add('a', 'b')); // 'ab'
  • 자바스크립트는 동적 타입 언어이기에 매개변수 타입을 사전에 지정하지 않는다.
  • 숫자 연산은 수학의 덧셈 결과를 return 했고, string 값은 concatenate 연산 결과를 return 한 것을 볼 수 있다.

이를 해결하기 위해 아래와 같이 코드를 작성할 수도 있다.

function add(x, y) { if (typeof x !== 'number' || typeof y !== 'number') { // 매개변수 타입을 모두 number 로 지정한다. 그렇지않으면 오류 발생 throw new TypeError('인수는 모두 숫자 값이어야 합니다.'); } return x + y; } console.log(add(2)); // TypeError:인수는 모두 숫자 값이어야 합니다. console.log(add('a', 'b')); // TypeError:인수는 모두 숫자 값이어야 합니다.

하지만 우리는 위와같이 코드를 작성하기 보다, 정적 타입을 선언할 수 있는 타입스크립트를 이용하면 컴파일 시점에 미리 부적절한 호출을 방지할 수 있다!

12-5-3 매개변수는 최대개수

  • 매개변수는 순서에 의미가 있고, 많을 수록 사용법이 어려워진다.
  • 이는 유지보수성이 나빠짐을 의미한다.

즉, 함수의 매개변수는 코드를 이해하는데의 방해 요소이므로 가장 이상적인 것은 매개변수의 개수가 0개인 것이며, 적을 수록 좋다. 최대 3개 이상을 넘기지 말자!

매개변수 기본값

function greet(name = 'Guest') { console.log(`Hello, ${name}`); } greet(); // Hello, Guest greet('KSJ'); // Hello, KSJ

ES6부터 매개변수에 기본값을 직접 설정할 수 있다.

  • 함수 호출 시 인자를 생략하거나 undefined를 전달하면 기본값 사용한다.
  • 기본값 표현식에서 다른 매개변수를 참조 가능. (ex : “KSJ” 전달)
  • 매개변수의 기본값을 이용하는 것은 인수를 전달하지 않았을 경우, 혹은 undefined 를 전달한 경우에만 유효하다.

반환문

return 키워드와 표현식(반환값)으로 이루어져 있고, 함수 실행 결과를 함수 외부로 반환한다.

주의할점

function multiply(x, y) { return x * y; // 반환문 console.log('실행안됨'); // 반환문 이후에 다른 문은 실행되지 않는다. } console.log(multiply(3, 5)); // 15
  • 반환문 이후에 다른 문은 실행되지 않는다.
function foo() { return; } console.log(foo()); // undefined
  • 반환문은 생략할 수 있다. 암묵적으로 undefined 를 반환한다.

12-6 참조에 의한 전달과 외부 상태의 변경

복습

  • 원시 값은 값에 의한 전달(pass by value)
  • 객체는 참조에 의한 전달(pass by reference)

따라서 함수의 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급되며 타입에 따라 값에의한 전달, 참조에 의한 전달 방식을 따른다.

예제

// 매개변수 primitive는 원시 값을 전달받고, 매개변수 obj는 객체를 전달받는다. function changeVal(primitive, obj) { primitive += 100; obj.name = 'KIM'; } // 외부상태 var num = 100; var person = { name: 'LEE' }; // 원시값은 자체가 값 자체가 복사되어 전달되고 객체는 참조 값이 복사되어 전달된다. changeVal(num, person); // 원시 값은 원본이 훼손되지 않는다. console.log(num); // 100 // 객체는 원본이 훼손된다. console.log(person); // { name: 'KIM' }
  • 원시 값은 원본이 훼손되지 않는다. (원본값을 복사한 값으로 전달하기 때문 )
  • 객체는 원본이 훼손된다.

위와 같은 방식으로 객체를 할당한 person 변수를 변경하면 상태 변화를 추적하기 어려워진다.이는 코드 복잡성 증가 및 가독성 감소의 원인이 된다.

이러한 문재의 해결법은 객체를 불변객체로 만들어 사용하는 것이다.(현재 단원에서 다루진 않음!)

  • Object.freeze()

    • 예시 코드
    const person = { name: 'LEE' }; Object.freeze(person); person.name = 'KIM'; // 무시됨 (strict 모드에서는 TypeError) console.log(person.name); // "LEE"
  • 불변객체를 복사로 관리하기 (많이 사용)

    • 예시 코드
    const person = { name: 'LEE' }; // 새로운 객체로 복사 후 값 변경 const newPerson = { ...person, name: 'KIM' }; console.log(person.name); // "LEE" (원본 불변) console.log(newPerson.name); // "KIM" (새 객체)

12-7 다양한 함수의 형태

즉시 실행 함수

함수의 정의와 동시에 즉시 호출되는 함수

아래는 자바스크립트의 함수 선언문이다.

function sayHi() { console.log('Hi'); } // function sayHi() {}(); // SyntaxError

선언문은 값처럼 즉시 실행할 수 없다. 왜냐면 JS 엔진은 function sayHi()를 선언으로 인식하지,표현식(값)으로 보지 않기 때문이다.

그래서 아래와 같이 ( ) 그룹연산자로 한 번 감싸준다.

(function sayHi() { console.log('Hi'); });

이제 위 예제코드는 함수 표현식이 된다. 즉, 하나의 값(value) 으로 평가된다.

특징

  • 단 한번만 호출되며 재호출 불가
(function () { var a = 3; var b = 5; return a * b; })();
  • 즉시 실행함수는 일반적으로 익명함수를 사용한다.
  • 이름이 있는 기명 함수도 즉시실행함수로 만들 수 있지만, 이것 역시 재호출은 불가능하다.
  • 즉시실행함수는 반드시 () 괄호 안에서 작성되어야한다.
    • 이 괄호를 그룹 연산자 라고 한다.
function () { // SyntaxError: Function statements require a function name //..... }();
  • 함수 선언문은 함수 이름을 생략할 수 없기 때문에 에러 발생
function foo() { //.... }(); // SyntaxError: Unexpected token ')'
  • 이건 왜 에러가 날까?
    • 세미콜론 자동 삽입기능에 의해 함수 선언문이 끝나는 위치인 ”}” 뒤에 ; 이 암묵적으로 삽입된다. 즉, };(); 이런 상태가 되는 것이다.

즉, 즉시실행함수를 이용하기 위해선 그룹연산자 () 로 먼저 함수를 묶어주는 것이 가장 중요하다!

즉시실행 함수도 일반함수처럼 값을 반환할 수 있고 인수를 전달할 수 있다.

// 값 반환하기 var res = (function () { var a = 3; var b = 5; return a * b; })(); console.log(res); // 15 // 인수 전달하기 res = (function (a, b) { return a * b; })(3, 5); console.log(res); // 15

참고) 즉시실행 함수의 형태 파헤쳐보기

// 매개변수가 없는경우 (function () { console.log('즉시 실행!'); })(); //매개변수가 있는 경우 (function (name) { console.log(`Hello, ${name}`); })('Seokjin'); // 화살표 함수 형태 (() => { console.log('Hello DeepDive!'); })();

즉시 실행함수는 위와같은 형태를 띈다. 이때, 각 위치의 괄호들이 어떤 의미인지 파악해보자!

  1. 가장 바깥의 ( ) : 그룹연산자

    -> 함수 선언을 함수 표현식으로 바꿔주는 역할

  2. 중괄호 : 함수의 본문(body)

    -> 실행할 코드 블록이 들어간다.

  3. 마지막의 () : 함수 호출 연산자

    -> 평소에 사용하는 f() 의 괄호라고 생각할 수 있다. 정의한 함수를 호출하는 역할.

재귀함수

함수가 자기 자신을 호출하는 것을 재귀호출이라 하며, 재귀호출을 수행하는 함수

function countdown(n) { if (n < 0) return; console.log(n); countdown(n - 1); // 재귀 호출 } countdown(10);
  • 주로 반복을 처리하기 위해 사용한다.
  • 반드시 재귀함수 내에 재귀호출을 멈출 수 있는 탈출조건(Base case)이 있어야한다. 그렇지 않으면 함수가 무한호출 되어 스택 오버플로 에러가 발생한다.
  • 대부분의 재귀함수는 for문이나 while 문으로 구현 가능하다.

위 예제의 실행결과를 예상해봅시다!

예제

function factorial(n) { // todo : 탈출조건 만들기 (n이 1 이하일때 재귀호출 멈추기) // todo : 재귀호출 } console.log(factorial(0)); console.log(factorial(4)); console.log(factorial(5));

해답

function factorial(n) { // todo : 탈출조건 만들기 (n이 1 이하일때 재귀호출 멈추기) if (n <= 1) return 1; // todo : 재귀호출 return n * factorial(n - 1); } console.log(factorial(0)); console.log(factorial(4)); console.log(factorial(5));

콜백함수

다른 함수의 인자로 전달되어, 특정 시점에 “호출(callback)“되는 함수

function greeting(name) { console.log(`안녕, ${name}!`); } function processUserInput(callback) { const name = 'Seokjin'; callback(name); } processUserInput(greeting); // 안녕, Seokjin!
함수 이름역할분류
greeting나중에 호출될 함수콜백 함수 (Callback)
processUserInput함수를 인자로 받아 내부에서 실행하는 함수고차 함수 (Higher-Order Function)

순수 함수와 비순수 함수

순수 함수(Pure Function) :

동일한 입력에는 항상 동일한 출력을 반환하고, 외부 상태를 변경하지 않는 함수

예시코드

function add(a, b) { return a + b; } console.log(add(3, 5)); // 8 console.log(add(3, 5)); // 8 <- 같은 결과
const num = 10; function addTen(x) { return x + 10; // num을 변경하지 않음 } console.log(addTen(num)); // 20 console.log(num); // 10 원본 유지
  • 즉 순수함수는 부작용(side effect)이 없는 함수

비순수 함수(Impure Function) :

외부 상태에 의존하거나 외부 상태를 변경하는 함수

예시코드

  1. 외부 변수에 의존하는 경우
let taxRate = 0.1; function calculatePrice(price) { return price + price * taxRate; } console.log(calculatePrice(100)); // 110 taxRate = 0.2; console.log(calculatePrice(100)); // 120 <-같은 입력인데 결과가 달라진다.
  1. 외부 상태 변경
let count = 0; function increase() { count++; return count; } console.log(increase()); // 1 console.log(increase()); // 2
  • 함수가 외부 변수(count)를 직접 변경하고 있다.
  • 비순수 함수는 부작용(side effect)이 있다.

순수함수의 장점

이유설명
예측 가능성항상 같은 입력 -> 같은 결과
테스트 용이외부 환경과 독립되어 단위 테스트가 쉬움
디버깅 용이어디서 값이 바뀌는지 추적 쉬움
함수형 프로그래밍 기반React, Redux 등은 불변성과 순수함수를 핵심 철학으로 함

함수형프로그래밍(순수함수&보조함수 조합으로 외부상태 변경을 최소화하여 불변성을 지향하는 패러다임)은 순수 함수를 통해 side effect 를 최대한 억제하여 오류를 피하고 안정성을 높이려는 노력의 일환이다.

자바스크립트는 멀티 패러다임 언어로 객체지향 프로그래밍과 함께 함수형 프로그래밍을 적극적으로 활용하고있다!

실습문제

출력 결과를 예상해보고 그 이유를 말해보자.

function multiple(x, y) { return; x * y; } console.log(multiple(3, 5));

해답

return 문 옆에 세미콜론이 없다. 하지만 세미콜론 자동 삽입 기능에 의해 세미콜론이 추가된다. 이후 x * y 는 무시된다. 따라서 undefined 가 반환된다.

함수표현식 ↔ 함수선언문 : 변경 예제

아래 함수 선언문을 함수표현식으로 변경하세요.

function greet(name) { return `안녕, ${name}!`; } function add(x, y) { return x + y; } // TODO: 위 함수를 함수 표현식 형태로 다시 작성해보세요.

해답

// 함수 표현식 const greet = function (name) { return `안녕, ${name}!`; }; const add = function (x, y) { return x + y; };

함수 표현식은 변수에 함수를 “값”으로 할당하는 형태로 작성되며,

세미콜론(;)을 붙이는 것이 일반적입니다.

실행 결과를 예측해보세요

console.log(square(3)); function square(x) { return x * x; } console.log(double(3)); var double = function (x) { return x * 2; };

해답

9 TypeError: double is not a function
  • square함수 선언문으로 정의되어, 호이스팅 시 전체 함수가 끌어올려져 정상 실행됩니다.
  • double함수 표현식으로, 변수 선언만 호이스팅되어 undefined 상태이므로 호출 시 오류가 발생합니다.

콜백함수 연습

function calculate(a, b, callback) { const result = callback(a, b); console.log(`결과: ${result}`); } // TODO: calculate 함수를 호출해서 3 + 4 의 결과가 출력되도록 해보세요. // (힌트: 두 수를 더하는 콜백 함수를 직접 전달해야 합니다!)

해답

function calculate(a, b, callback) { const result = callback(a, b); console.log(`결과: ${result}`); } calculate(3, 4, (x, y) => x + y);
Last updated on